﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace DevOnServer
{
    public class CWorld : CTimed
    {
        public CWorld(CGameServer server, int id, string name, int fieldSize)
        {
            Server = server;
            ID = id;
            Name = name;
            m_fieldSize = fieldSize;
            Width = 5 * 512;
            Height = 5 * 512;
        }

        public bool init()
        {
            #region Chargement du wld

            CFileReader file = new CFileReader();
            if (!file.load(string.Format("World/{0}/{0}.wld", Name)))
            {
                Console.WriteLine("Can't open world file 'World/{0}/{0}.wld'", Name);
                return false;
            }

            string tok;
            while (!file.end)
            {
                tok = file.getString().ToLower();

                if (tok == "size")
                {
                    Width = int.Parse(file.getString().Replace(',', ' '));
                    Height = int.Parse(file.getString().Replace(',', ' '));
                }
                else if (tok == "mpu")
                {
                    m_mpu = file.getInt();
                }
                else if (tok == "indoor")
                {
                    m_inDoor = file.getBool();
                }
            }

            Width *= 128 * m_mpu;
            Height *= 128 * m_mpu;

            #endregion
            #region Création des fields

            m_fields = new CWorldField[Width / m_fieldSize, Height / m_fieldSize];
            for (int x = 0; x < Width / m_fieldSize; x++)
                for (int y = 0; y < Height / m_fieldSize; y++)
                    m_fields[x, y] = new CWorldField(new CVec2Di(x, y));

            #endregion
            #region Chargement du rgn

            file = new CFileReader();
            if (!file.load(string.Format("World/{0}/{0}.rgn", Name)))
            {
                Console.WriteLine("Can't open region file 'World/{0}/{0}.rgn'", Name);
                return false;
            }

            ObjType objType;
            int modelId, count, time, aggro, type, worldId, temp;
            float height;
            CVec2Di min, max;
            CVec3Df teleportPos;

            while (!file.end)
            {
                tok = file.getString();
                if (tok == "respawn7")
                {
                    objType = file.getInt() == 4 ? ObjType.Drop : ObjType.Monster;
                    modelId = file.getInt();
                    file.move(1); // PosX
                    height = file.getFloat();
                    file.move(1); // PosZ
                    count = file.getInt();
                    time = file.getInt();
                    aggro = file.getInt();
                    min.X = file.getInt();
                    min.Y = file.getInt();
                    max.X = file.getInt();
                    max.Y = file.getInt();
                    file.move(11);

                    m_respawners.Add(new CRespawner(this, objType, modelId, height, count, time * 1000, aggro, min, max));
                }
                else if (tok == "region3")
                {
                    file.move(1); // 6
                    type = file.getInt();
                    file.move(8); // center, attributes, musicId, structure...
                    worldId = file.getInt();
                    teleportPos = new CVec3Df(file.getFloat(), file.getFloat(), file.getFloat());
                    min.X = file.getInt();
                    min.Y = file.getInt();
                    max.X = file.getInt();
                    max.Y = file.getInt();
                    file.move(14); // name + ? + title

                    temp = file.getInt();
                    if (temp > 0)
                        file.move(2 + temp);

                    file.move(1); // desc
                    temp = file.getInt();
                    if (temp > 0)
                        file.move(2 + temp);

                    if (type == 10 && worldId != 0)
                    {
                        m_regions.Add(new CRegion()
                        {
                            Type = RegionType.WarpZone,
                            MinPos = new CVec2Df((float)min.X, (float)min.Y),
                            MaxPos = new CVec2Df((float)max.X, (float)max.Y),
                            TeleportWorldId = worldId,
                            TeleportWorldPos = teleportPos
                        });
                    }
                }
            }

            #endregion
            #region Chargement du dyo

            BinaryReader reader = null;
            try
            {
                reader = new BinaryReader(File.OpenRead(string.Format("World/{0}/{0}.dyo", Name)));
            }
            catch (Exception)
            {
                Console.WriteLine("Can't open dyo file 'World/{0}/{0}.dyo'", Name);
            }

            float angle;
            CVec3Df rot, pos, scale;
            int id, aiType;

            type = reader.ReadInt32();
            while (type != -1)
            {
                switch (type)
                {
                    case 0:
                    case 4:
                    case 6:
                        _readDyoObject(reader, out angle, out rot, out pos, out scale, out id, out aiType);
                        break;

                    case 2:
                        _readDyoObject(reader, out angle, out rot, out pos, out scale, out id, out aiType);

                        CControlObject ctrl = new CControlObject(_generateObjectId(), this);

                        ctrl.Size = (short)(scale.X * (float)ctrl.Size);
                        ctrl.Position = pos;
                        ctrl.Rotation = (short)(angle * 10.0f);

                        ctrl.PropID = id;

                        addObject(ctrl);

                        uint condition = reader.ReadUInt32();
                        switch (condition)
                        {
                            case 0x80000000:
                                reader.ReadBytes(432);
                                break;
                            case 0x90000000:
                                reader.ReadBytes(88);
                                reader.ReadBytes(280);
                                break;
                            default:
                                reader.ReadBytes(392);
                                break;
                        }
                        break;

                    case 5:
                        _readDyoObject(reader, out angle, out rot, out pos, out scale, out id, out aiType);

                        CNPCObject npc = new CNPCObject(_generateObjectId(), this, Server.DataManager.MoverProperties[id]);

                        reader.ReadBytes(64); //korean name
                        reader.ReadBytes(32); //dialof file name

                        npc.Name = Encoding.Default.GetString(reader.ReadBytes(32));
                        npc.Name = npc.Name.Substring(0, npc.Name.IndexOf('\0'));

                        reader.ReadInt32();
                        reader.ReadInt32();

                        npc.Size = (short)(scale.X * (float)npc.Size);
                        npc.Position = pos;
                        npc.Rotation = (short)(angle * 10.0f);

                        addObject(npc);

                        break;
                }
                type = reader.ReadInt32();
            }

            reader.Close();

            #endregion

            if (ID == (int)DefineWorld.WI_WORLD_KEBARAS)
            {
                m_weather = Weather.Snow;
            }

            return true;
        }

        private void _readDyoObject(BinaryReader r, out float angle, out CVec3Df rotation, out CVec3Df position, out CVec3Df scale, out int id, out int aiType)
        {
            // merci OSAFlyff !
            // lecture du dyo à réorganiser

            angle = r.ReadSingle();

            rotation = new CVec3Df()
            {
                X = r.ReadSingle(),
                Y = r.ReadSingle(),
                Z = r.ReadSingle()
            };

            position = new CVec3Df()
            {
                X = r.ReadSingle() * 4.0f,
                Y = r.ReadSingle(),
                Z = r.ReadSingle() * 4.0f
            };

            scale = new CVec3Df()
            {
                X = r.ReadSingle(),
                Y = r.ReadSingle(),
                Z = r.ReadSingle()
            };

            r.ReadInt32();
            id = r.ReadInt32();
            r.ReadInt32();
            aiType = r.ReadInt32();
            r.ReadInt32();
        }

        public override void update()
        {
            if (PlayerCount == 0)
                return;

            for (int i = m_objects.Count - 1; i >= 0; i--)
            {
                m_objects[i].update();
            }

            for (int i = m_respawners.Count - 1; i >= 0; i--)
            {
                m_respawners[i].update();
            }

            if (!m_inDoor
                && ID != (int)DefineWorld.WI_WORLD_KEBARAS
                && s_time >= m_nextWeatherChange)
            {
                m_weather = (Weather)CRandom.random(0, 3);

                sendToAllPlayers(CPacketCreator.weather(m_weather));

                if (m_weather == Weather.Normal)
                    m_nextWeatherChange = s_time + CRandom.random(3000000, 3600000);
                else
                    m_nextWeatherChange = s_time + CRandom.random(1200000, 1800000); // j'aime pas la pluie et la neige :D
            }
        }

        public CDropObject createDropObject(int propId, CVec3Df position, CPlayerObject owner)
        {
            return createDropObject(Server.DataManager.ItemProperties[propId], position, owner);
        }

        public CDropObject createDropObject(CItemProp prop, CVec3Df position, CPlayerObject owner)
        {
            CDropObject obj = new CDropObject(_generateObjectId(), this);

            obj.ItemProp = prop;
            obj.Position = position;
            obj.Rotation = (short)CRandom.random(0, 3599);
            obj.Owner = owner;

            addObject(obj);

            return obj;
        }

        public CMonster createMonsterObject(CMoverProp prop, CVec3Df position)
        {
            CMonster obj = new CMonster(_generateObjectId(), this, prop, position);

            obj.Position = position;
            obj.Rotation = (short)CRandom.random(0, 3599);

            addObject(obj);

            return obj;
        }

        public CWorldObject getObject(int id)
        {
            foreach (CWorldObject obj in m_objects)
            {
                if (obj.ID == id)
                {
                    return obj;
                }
            }

            return null;
        }

        public void updateField(CWorldObject obj)
        {
            if (obj.Type == ObjType.Player) // check les warpzones
            {
                foreach (CRegion region in m_regions)
                {
                    if (region.Type == RegionType.WarpZone
                        && obj.Position.X > region.MinPos.X
                        && obj.Position.X < region.MaxPos.X
                        && obj.Position.Z > region.MinPos.Y
                        && obj.Position.Z < region.MaxPos.Y)
                    {
                        if (Server.Worlds.ContainsKey(region.TeleportWorldId))
                            (obj as CPlayerObject).teleport(region.TeleportWorldPos, Server.Worlds[region.TeleportWorldId]);
                    }
                }
            }

            CWorldField field = getFieldByPosition(obj.Position);

            if (field != obj.Field)
            {
                _changeField(obj, obj.Field, field);
            }
        }

        private void _changeField(CWorldObject obj, CWorldField oldField, CWorldField newField)
        {
            if (oldField != null)
            {
                oldField.removeObject(obj);

                if (newField == null)
                {
                    sendToLocalArea(obj, CPacketCreator.despawn(obj));
                }
            }

            if (newField != null)
            {
                newField.addObject(obj);

                if (oldField != null)
                {
                    CVec2Di start, end;
                    int x, y;

                    _getCorrectedFieldRange(out start, out end, oldField);

                    CWorldField field;

                    for (y = start.Y; y <= end.Y; y++)
                    {
                        for (x = start.X; x <= end.X; x++)
                        {
                            field = m_fields[x, y];

                            if (!newField.isFieldInArea(field))
                            {
                                if (field.PlayerCount > 0)
                                {
                                    if (obj.Type == ObjType.Player)
                                    {
                                        field.sendToAllPlayersBut(CPacketCreator.despawn(obj), (CPlayerObject)obj);
                                    }
                                    else
                                    {
                                        field.sendToAllPlayers(CPacketCreator.despawn(obj));
                                    }
                                }

                                if (obj.Type == ObjType.Player)
                                {
                                    field.despawnAll((CPlayerObject)obj);
                                }
                            }
                        }
                    }

                    _getCorrectedFieldRange(out start, out end, newField);

                    for (y = start.Y; y <= end.Y; y++)
                    {
                        for (x = start.X; x <= end.X; x++)
                        {
                            field = m_fields[x, y];

                            if (!oldField.isFieldInArea(field))
                            {
                                if (field.PlayerCount > 0)
                                {
                                    if (obj.Type == ObjType.Player)
                                    {
                                        field.spawnAll((CPlayerObject)obj);
                                        field.sendToAllPlayersBut(CPacketCreator.spawn(obj), (CPlayerObject)obj);
                                    }
                                    else
                                    {
                                        field.sendToAllPlayers(CPacketCreator.spawn(obj));
                                    }
                                }
                                else if (obj.Type == ObjType.Player)
                                {
                                    field.spawnAll((CPlayerObject)obj);
                                }
                            }
                        }
                    }
                }
                else
                {
                    CVec2Di start, end;
                    int x, y;

                    _getCorrectedFieldRange(out start, out end, newField);

                    CWorldField field;

                    if (obj.Type == ObjType.Player)
                    {
                        for (y = start.Y; y <= end.Y; y++)
                        {
                            for (x = start.X; x <= end.X; x++)
                            {
                                field = m_fields[x, y];

                                if (field.PlayerCount > 0)
                                {
                                    field.sendToAllPlayersBut(CPacketCreator.spawn(obj), (CPlayerObject)obj);
                                }

                                field.spawnAll((CPlayerObject)obj);
                            }
                        }
                    }
                    else
                    {
                        for (y = start.Y; y <= end.Y; y++)
                        {
                            for (x = start.X; x <= end.X; x++)
                            {
                                field = m_fields[x, y];

                                if (field.PlayerCount > 0)
                                {
                                    field.sendToAllPlayers(CPacketCreator.spawn(obj));
                                }
                            }
                        }
                    }
                }
            }
        }

        public CWorldField getFieldByPosition(CVec3Df pos)
        {
            if (pos.X < 0
                || pos.Z < 0
                || pos.X >= Width
                || pos.Z >= Height)
                return m_fields[0, 0];

            return m_fields[(int)(pos.X / (float)m_fieldSize), (int)(pos.Z / (float)m_fieldSize)];
        }

        public void sendToLocalArea(CWorldObject obj, CPacket packet)
        {
            CVec2Di start, end;
            int x, y;

            _getCorrectedFieldRange(out start, out end, obj.Field);

            CWorldField field;

            for (y = start.Y; y <= end.Y; y++)
            {
                for (x = start.X; x <= end.X; x++)
                {
                    field = m_fields[x, y];

                    if (field.PlayerCount > 0)
                    {
                        field.sendToAllPlayers(packet);
                    }
                }
            }
        }

        public void sendToAllPlayers(CPacket packet)
        {
            foreach (CWorldObject obj in m_objects)
            {
                if (obj.Type == ObjType.Player)
                {
                    (obj as CPlayerObject).Player.Client.send(packet);
                }
            }
        }

        public void sendToLocalArea(CWorldObject obj, CPacket packet, CPlayerObject noSend)
        {
            CVec2Di start, end;
            int x, y;

            _getCorrectedFieldRange(out start, out end, obj.Field);

            CWorldField field;

            for (y = start.Y; y <= end.Y; y++)
            {
                for (x = start.X; x <= end.X; x++)
                {
                    field = m_fields[x, y];

                    if (field.PlayerCount > 0)
                    {
                        field.sendToAllPlayersBut(packet, noSend);
                    }
                }
            }
        }

        public void noTarget(CWorldObject obj)
        {
            foreach (CWorldObject o in m_objects)
            {
                if (o.Type == ObjType.Monster)
                {
                    CMonster mon = (CMonster)o;

                    if (mon.Followed == obj)
                        mon.Followed = null;

                    if (obj.Type == ObjType.Player
                        && mon.AttackTarget == (CPlayerObject)obj)
                        mon.AttackTarget = null;
                }
                else if (o.Type == ObjType.Player)
                {
                    CPlayerObject player = (CPlayerObject)o;

                    if (player.Followed == obj)
                        player.Followed = null;
                }
            }
        }

        public void addPlayer(CGamePlayer player)
        {
            if (player.Object != null)
            {
                if (player.Object.World != null)
                {
                    player.Object.World.removeObject(player.Object);
                }

                player.Object.World = this;

                player.Client.send(CPacketCreator.teleportWorld(player.Object));
                player.Client.send(CPacketCreator.worldReadInfo(player.Object));
                player.Client.send(CPacketCreator.worldBegin(player));

                m_objects.Add(player.Object);
            }
            else
            {
                player.Object = new CPlayerObject(player, _generateObjectId(), this);
                m_objects.Add(player.Object);

                Server.Database.loadInventory(player.Object.Inventory);

                player.Object.calculateStats();

                player.Client.send(CPacketCreator.worldReadInfo(player.Object));
                player.Client.send(CPacketCreator.worldBegin(player));
            }

            player.Client.send(CPacketCreator.weather(m_weather));

            if (player.Object.Position.X < 0.0f)
                player.Object.Position.X = 0.0f;
            else if (player.Object.Position.X > Width)
                player.Object.Position.X = Width;

            if (player.Object.Position.Y < 0.0f)
                player.Object.Position.Y = 0.0f;
            else if (player.Object.Position.Y > Height)
                player.Object.Position.Y = Height;

            updateField(player.Object);
            PlayerCount++;
        }

        public void addObject(CWorldObject obj)
        {
            if (obj.Type == ObjType.Player)
            {
                addPlayer((obj as CPlayerObject).Player);
            }
            else
            {
                m_objects.Add(obj);
                obj.World = this;
                updateField(obj);
            }
        }

        public void removeObject(CWorldObject obj)
        {
            if (!m_objects.Contains(obj))
                return;

            _changeField(obj, obj.Field, null);

            obj.onWorldRemove();
            m_objects.Remove(obj);

            if (obj.Type == ObjType.Player)
                PlayerCount--;
        }

        public void transfertObject(CWorldObject obj, CWorld newWorld)
        {
            removeObject(obj);
            newWorld.addObject(obj);
        }

        private void _getCorrectedFieldRange(out CVec2Di start, out CVec2Di end, CWorldField field)
        {
            start = field.Position;
            end = start;

            if (start.X >= 1)
                start.X -= 1;

            if (start.Y >= 1)
                start.Y -= 1;

            end.X += 1;
            end.Y += 1;

            if (end.X >= m_fields.GetLength(0))
                end.X = m_fields.GetLength(0) - 1;

            if (end.Y >= m_fields.GetLength(1))
                end.Y = m_fields.GetLength(1) - 1;
        }

        public int ID;

        public string Name;

        public int PlayerCount { get; private set; }

        public int ObjectCount
        {
            get
            {
                return m_objects.Count;
            }
        }

        public CGameServer Server { get; private set; }

        private int m_fieldSize;

        public int Width { get; private set; }

        public int Height { get; private set; }

        private static int s_nextObjectId;

        private static int _generateObjectId()
        {
            return s_nextObjectId++;
        }

        private CWorldField[,] m_fields;

        private List<CWorldObject> m_objects = new List<CWorldObject>();

        private List<CRespawner> m_respawners = new List<CRespawner>();

        private List<CRegion> m_regions = new List<CRegion>();

        private int m_mpu = 4;

        private bool m_inDoor = false;

        private Weather m_weather = Weather.Normal;

        private int m_nextWeatherChange;
    }

    public enum Weather
    {
        Normal,
        Rain,
        Snow
    }
}